You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In Session.resendMessages, when the last retrieved persisted message is an admin message, appMessageJustSent is false at loop end, so newBegin is never updated from beginSeqNo. The subsequent generateSequenceResetIfNeeded call emits a SequenceReset-GapFill with a MsgSeqNum that is too low — overlapping application messages already resent to the counterparty.
Change
Adds two failing regression tests to SessionTest (sharing a common private helper) that isolate the scenario with and without enableNextExpectedMsgSeqNum:
Seqno 3–4: app (ExecutionReport) — persisted, will be resent
Seqno 5: admin (Heartbeat) — persisted, last retrieved message
Seqno 6–10: admin — not persisted, NextSenderMsgSeqNum = 11
When the counterparty sends ResendRequest(BeginSeqNo=1), the expected final SequenceReset is:
SequenceReset(MsgSeqNum=6, NewSeqNo=11) ← correct: covers gap 6–10
Current code produces:
SequenceReset(MsgSeqNum=1, NewSeqNo=11) ← bug: beginSeqNo used instead of lastAppSeqNo+1
The test asserts MsgSeqNum=6 and intentionally fails on the current code (expected:<[6]> but was:<[1]>).
Scenario (enableNextExpectedMsgSeqNum=true):
With this mode, the session does not send an explicit ResendRequest at logon time (the resend is implicit via tag 789), so app messages occupy seqnos 2–4 instead of 3–4. The bug manifests differently: the post-loop branch in resendMessages skips generateSequenceResetIfNeeded entirely, omitting the trailing gap-fill SequenceReset. The test expects 7 outgoing messages (the last being SequenceReset(MsgSeqNum=6, NewSeqNo=11)) and intentionally fails on the current code with only 6 messages sent.
The root cause — newBegin should be updated inside the loop each time an app message is successfully resent, not only after the loop based on the last message type — is left for a separate fix PR.
Original prompt
Create a PR against master in repository quickfix-j/quickfixj that adds a unit test which reproduces issue #344 (bug in Session.resendMessages / generateSequenceResetIfNeeded involving newBegin).
Requirements:
Do not fix production code behavior. Only add a test that demonstrates the problematic behavior.
The test should set up a resend range like beginSeqNo=2, endSeqNo=10 (or similar) where the message store returns only a subset (e.g., 2..6) and within those:
message 5 is an application message that is successfully resent (so the counterparty would advance expected sender seqnum),
message 6 is an admin message, which causes appMessageJustSent to be false at loop end and newBegin to remain beginSeqNo.
Assert that the final SequenceReset generated by generateSequenceResetIfNeeded uses an incorrect begin sequence number (too low, e.g. begins at 2 instead of 6). The test should fail on current behavior to highlight the bug.
Prefer asserting on the actual outgoing raw messages captured by a test responder / in-memory session transport (e.g., capture sendRaw / outgoing queue) rather than calling private methods directly.
Keep the test deterministic and isolated (in-memory message store or stubbed SessionState/store).
Add the test under the appropriate module (likely quickfixj-core) using the existing test framework already used in the repo.
Context:
Suspected buggy area in quickfixj-core/src/main/java/quickfix/Session.java around the computation of newBegin after the resend loop and call to generateSequenceResetIfNeeded.
Related code: resendMessages(...) and generateSequenceResetIfNeeded(...).
One new unit test (and any minimal test fixtures) that reproduces the behavior.
PR description should explain the scenario and that the test currently fails, serving as a regression test for a future fix.
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Could you please help me understand this issue? Check the position of the possible bug in the screenshot
Assistant: Now I have a clear picture of the full resendMessages method. Let me put it all together to explain the bug.
Understanding the Bug in resendMessages / generateSequenceResetIfNeeded
Here's the relevant section of the code at the end of resendMessages, which is where the bug lives:
intnewBegin = beginSeqNo; // ← initialized to the original start of the rangeif (appMessageJustSent) {
newBegin = msgSeqNum + 1; // ← updated ONLY if the LAST message was an app message
}
if (enableNextExpectedMsgSeqNum) {
if (begin != 0) {
generateSequenceReset(receivedMessage, begin, msgSeqNum + 1);
} else {
generateSequenceResetIfNeeded(receivedMessage, newBegin, endSeqNo, msgSeqNum);
}
} else {
if (begin != 0) {
generateSequenceReset(receivedMessage, begin, msgSeqNum + 1);
}
generateSequenceResetIfNeeded(receivedMessage, newBegin, endSeqNo, msgSeqNum);
}
The Bug — Step by Step
Consider resending messages 2 to 10, but persistence only has messages 2 to 6:
SeqNo
Type
Action
2
App
Resent ✅ → appMessageJustSent = true, begin = 0
3
App
Resent ✅ → appMessageJustSent = true, begin = 0
4
App
Resent ✅ → appMessageJustSent = true, begin = 0
5
App
Resent ✅ → appMessageJustSent = true, begin = 0
6
Admin
Skipped → begin = 6, appMessageJustSent = false
After the loop:
appMessageJustSent = false (because the last message processed was an admin message at seqno 6)
msgSeqNum = 6 (last message in persistence)
begin = 6 (set when admin message was encountered)
Because appMessageJustSent is false, newBegin stays at its initial value: beginSeqNo = 2.
Then generateSequenceResetIfNeeded is called with beginSeqNo = 2, sending a SequenceReset (gap-fill) FROM 2 TO 11.
But the counterparty already processed app messages 2–5, and is now expecting seqno 6. A SequenceReset from seqno 2 will be rejected as "sequence number too low"!
The Root Cause
The newBegin variable is only updated after the loop based on whether the very last message was an app message. It doesn't track the "beginning of the trailing gap" correctly when the sequence ends with one or more admin messages after the last app message.
// Current (buggy): newBegin only updates if LAST msg was app
int newBegin = beginSeqNo;
if (appMessageJustSent) { // false because last msg was admin!
newBegin = msgSeqNum + 1;
}
CopilotAI
changed the title
[WIP] Add unit test reproducing issue #344 in Session.resendMessages
test: regression test for newBegin bug in Session.resendMessages (issue #344)
Apr 24, 2026
@copilot please add another unit test that basically does the same as your newly added test, but make the session use "enableNextExpectedMsgSeqNum=true".
Re-use as much code as possible in the two tests but make distinctions where necessary.
Only add the test, do not fix the problem.
@copilot please add another unit test that basically does the same as your newly added test, but make the session use "enableNextExpectedMsgSeqNum=true".
Re-use as much code as poss...
Done. The existing test has been refactored to delegate to a shared private helper (doResendMessages_whenLastRetrievedMessageIsAdmin_scenario), and the new enableNextExpectedMsgSeqNum=true variant calls the same helper with true.
Both tests intentionally fail on the current code, but for different reasons:
enableNextExpectedMsgSeqNum=false: 7 messages are sent but the final SequenceReset has MsgSeqNum=1 instead of 6 → fails on the content assertion.
enableNextExpectedMsgSeqNum=true: the post-loop branch (if (begin != 0)) is taken but its else (which calls generateSequenceResetIfNeeded) is skipped, so the trailing gap SequenceReset is omitted entirely — only 6 messages are sent → fails on verify(mockResponder, times(7)).
CopilotAI
changed the title
test: regression test for newBegin bug in Session.resendMessages (issue #344)
test: regression tests for newBegin bug in Session.resendMessages (issue #344)
Jun 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In
Session.resendMessages, when the last retrieved persisted message is an admin message,appMessageJustSentisfalseat loop end, sonewBeginis never updated frombeginSeqNo. The subsequentgenerateSequenceResetIfNeededcall emits a SequenceReset-GapFill with aMsgSeqNumthat is too low — overlapping application messages already resent to the counterparty.Change
Adds two failing regression tests to
SessionTest(sharing a common private helper) that isolate the scenario with and withoutenableNextExpectedMsgSeqNum:Scenario (
enableNextExpectedMsgSeqNum=false):NextSenderMsgSeqNum = 11When the counterparty sends
ResendRequest(BeginSeqNo=1), the expected final SequenceReset is:Current code produces:
The test asserts
MsgSeqNum=6and intentionally fails on the current code (expected:<[6]> but was:<[1]>).Scenario (
enableNextExpectedMsgSeqNum=true):With this mode, the session does not send an explicit ResendRequest at logon time (the resend is implicit via tag 789), so app messages occupy seqnos 2–4 instead of 3–4. The bug manifests differently: the post-loop branch in
resendMessagesskipsgenerateSequenceResetIfNeededentirely, omitting the trailing gap-fill SequenceReset. The test expects 7 outgoing messages (the last beingSequenceReset(MsgSeqNum=6, NewSeqNo=11)) and intentionally fails on the current code with only 6 messages sent.The root cause —
newBeginshould be updated inside the loop each time an app message is successfully resent, not only after the loop based on the last message type — is left for a separate fix PR.Original prompt
Create a PR against
masterin repositoryquickfix-j/quickfixjthat adds a unit test which reproduces issue #344 (bug inSession.resendMessages/generateSequenceResetIfNeededinvolvingnewBegin).Requirements:
beginSeqNo=2,endSeqNo=10(or similar) where the message store returns only a subset (e.g., 2..6) and within those:appMessageJustSentto be false at loop end andnewBeginto remainbeginSeqNo.generateSequenceResetIfNeededuses an incorrect begin sequence number (too low, e.g. begins at 2 instead of 6). The test should fail on current behavior to highlight the bug.sendRaw/ outgoing queue) rather than calling private methods directly.SessionState/store).quickfixj-core) using the existing test framework already used in the repo.Context:
quickfixj-core/src/main/java/quickfix/Session.javaaround the computation ofnewBeginafter the resend loop and call togenerateSequenceResetIfNeeded.resendMessages(...)andgenerateSequenceResetIfNeeded(...).SequenceResetmight use wrongMsgSeqNum, message will be ignored by counterparty #344Deliverables:
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Could you please help me understand this issue? Check the position of the possible bug in the screenshot
Assistant: Now I have a clear picture of the full
resendMessagesmethod. Let me put it all together to explain the bug.Understanding the Bug in
resendMessages/generateSequenceResetIfNeededHere's the relevant section of the code at the end of
resendMessages, which is where the bug lives:The Bug — Step by Step
Consider resending messages 2 to 10, but persistence only has messages 2 to 6:
appMessageJustSent = true,begin = 0appMessageJustSent = true,begin = 0appMessageJustSent = true,begin = 0appMessageJustSent = true,begin = 0begin = 6,appMessageJustSent = falseAfter the loop:
appMessageJustSent = false(because the last message processed was an admin message at seqno 6)msgSeqNum = 6(last message in persistence)begin = 6(set when admin message was encountered)Because
appMessageJustSentisfalse,newBeginstays at its initial value:beginSeqNo= 2.Then
generateSequenceResetIfNeededis called withbeginSeqNo = 2, sending a SequenceReset (gap-fill) FROM 2 TO 11.But the counterparty already processed app messages 2–5, and is now expecting seqno 6. A SequenceReset from seqno 2 will be rejected as "sequence number too low"!
The Root Cause
The
newBeginvariable is only updated after the loop based on whether the very last message was an app message. It doesn't track the "beginning of the trailing gap" correctly when the sequence ends with one or more admin messages after the last app message.###...
This pull request was created from Copilot chat.